package cn.com.zte.ngbc.frw.model.util;

import cn.com.zte.ngbc.base.frw.model.util.ModelPropertyDescriptor;
import cn.com.zte.ngbc.base.logging.LogManager;
import cn.com.zte.ngbc.base.logging.SysLog;
import cn.com.zte.ngbc.base.odb.OdbFactory;
import cn.com.zte.ngbc.base.util.ConvertUtil;
import cn.com.zte.ngbc.base.util.EnumUtils;
import cn.com.zte.ngbc.base.util.StringUtil;
import cn.com.zte.ngbc.frw.model.ModelObject;
import cn.com.zte.ngbc.frw.model.ModelObjectCreator;
import cn.com.zte.ngbc.frw.model.ModelObjectCreator.ModelClassAware;
import cn.com.zte.ngbc.frw.model.ModelRelation;
import cn.com.zte.ngbc.frw.model.OwnerAware;
import cn.com.zte.ngbc.frw.model.datainterface.DataInterfaceField;
import cn.com.zte.ngbc.frw.model.dm.ComplexType;
import cn.com.zte.ngbc.frw.model.dm.Element;
import cn.com.zte.ngbc.frw.model.dm.ElementType;
import cn.com.zte.ngbc.frw.model.dm.Options;
import cn.com.zte.ngbc.frw.model.dm.RestrictionType;
import cn.com.zte.ngbc.frw.model.dm.Schema;
import cn.com.zte.ngbc.frw.model.dm.Schema.Classgen;
import cn.com.zte.ngbc.frw.model.dm.SimpleType;
import cn.com.zte.ngbc.lang.Symbol;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyModelObjectCreator
  extends ModelObjectCreator
{
  private static final SysLog log = LogManager.getSysLog(ProxyModelObjectCreator.class);
  private static final Object _NULL = new Object();
  
  public <T> T create(Class<T> t)
  {
    Class<T> t1 = super.resolveInterface(t);
    if (t1 != t) {
      return (T)super.create(t);
    }
    if (((((Boolean)LazyLoad.get()).booleanValue()) || (((Boolean)LazyResolve.get()).booleanValue())) && (XmlConfigManager.isCreatingModelObject())) {
      return (T)createProxyByCglib(t, ((Boolean)LazyLoad.get()).booleanValue(), ((Boolean)LazyResolve.get()).booleanValue());
    }
    if (t.isInterface()) {
      return (T)createProxyByCglib(t, false, false);
    }
    return (T)super.create(t);
  }
  
  public <T> T createPageModel(Class<T> t)
  {
    Class<T> t1 = super.resolveInterface(t);
    if (t1 != t) {
      return (T)super.create(t);
    }
    return (T)createProxyByCglib(t, true, ((Boolean)LazyResolve.get()).booleanValue());
  }
  
  public <T> T createProxyByCglib(Class<T> targetClass, boolean introducePartner, boolean lazyresolve)
  {
    Enhancer enhancer = new Enhancer();
    enhancer.setUseCache(true);
    List<Class<?>> superInterfaces = new ArrayList();
    if (targetClass.isInterface())
    {
      superInterfaces.add(targetClass);
      superInterfaces.add(Map.class);
    }
    superInterfaces.add(ModelObjectCreator.ModelClassAware.class);
    if (introducePartner) {
      superInterfaces.add(ModelObjectPartner.class);
    }
    if (!targetClass.isInterface()) {
      enhancer.setSuperclass(targetClass);
    }
    enhancer.setInterfaces((Class[])superInterfaces.toArray(new Class[0]));
    enhancer.setCallback(new ProxyModelObjectInterceptor(targetClass, lazyresolve, introducePartner, targetClass.isInterface()));
    
    return (T)enhancer.create();
  }
  
  private static class MapMethodCache
  {
    private static final MapMethodCache instance = new MapMethodCache();
    private static final Map<String, Object> cache = new ConcurrentHashMap();
    
    public static Method getMethod(String name, Class<?>... parameterTypes)
    {
      return instance._getMethod(name, parameterTypes);
    }
    
    private Method _getMethod(String key, Class<?>... parameterTypes)
    {
      Object ret = cache.get(key);
      if (ret == null) {
        try
        {
          ret = Map.class.getMethod(key, parameterTypes);
          if (ret == null) {
            cache.put(key, ProxyModelObjectCreator._NULL);
          } else {
            cache.put(key, ret);
          }
        }
        catch (NoSuchMethodException ignore)
        {
          cache.put(key, ProxyModelObjectCreator._NULL);
        }
      } else if (ret == ProxyModelObjectCreator._NULL) {
        return null;
      }
      if ((ret instanceof Method)) {
        return (Method)ret;
      }
      return null;
    }
  }
  
  private static final class ProxyModelObjectInterceptor
    implements MethodInterceptor
  {
    private final Class<?> proxyClass;
    private final Map<Symbol, Object> partner;
    private final Map<String, Object> map;
    private final boolean lazyresolve;
    private final boolean introducePartner;
    private final boolean introduceMap;
    private final Object hash = new Object();
    
    public ProxyModelObjectInterceptor(Class<?> proxyClass, boolean lazyresolve, boolean introducePartner, boolean introduceMap)
    {
      this.map = (introduceMap ? new HashMap() : null);
      this.partner = (introducePartner ? new HashMap() : null);
      this.proxyClass = proxyClass;
      this.lazyresolve = lazyresolve;
      this.introducePartner = introducePartner;
      this.introduceMap = introduceMap;
      if (this.map != null)
      {
        Map<String, Object> prototype = ProxyModelObjectCreator.getPrototype(proxyClass);
        if (prototype != null) {
          this.map.putAll(prototype);
        }
      }
    }
    
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
      throws Throwable
    {
      String mname = method.getName();
      int mname_len = mname.length();
      int mlen = method.getParameterTypes().length;
      if (method.getDeclaringClass() == ModelObjectCreator.ModelClassAware.class) {
        return this.proxyClass;
      }
      if ((this.introducePartner) && (method.getDeclaringClass() == ModelObjectPartner.class) && ("getData".equals(mname))) {
        return this.partner;
      }
      if (this.introduceMap) {
        if (mlen == 1)
        {
          if ((mname_len == 3) && ("get".equals(mname))) {
            return this.map.get(args[0]);
          }
          if ((mname_len == 6) && ("equals".equals(mname))) {
            return Boolean.valueOf(this.hash.equals(args[0]));
          }
          if ((mname_len > 3) && (method.getReturnType() == Void.TYPE) && (mname.startsWith("set")))
          {
            String property = ProxyModelObjectCreator.getPropertyName(method);
            if (property != null)
            {
              setProperty(obj, property, args[0]);
              return null;
            }
          }
        }
        else if (mlen == 2)
        {
          if ((mname_len == 3) && ("put".equals(mname)))
          {
            this.map.put((String)args[0], args[1]);
            
            return null;
          }
        }
        else if (mlen == 0)
        {
          if ((mname_len == 8) && ("toString".equals(mname))) {
            return this.proxyClass.getSimpleName() + StringUtil.mapToString(this.map);
          }
          if ((mname_len == 7) && ("isEmpty".equals(mname))) {
            return Boolean.valueOf(false);
          }
          if ((mname_len == 8) && ("hashCode".equals(mname))) {
            return Integer.valueOf(this.hash.hashCode());
          }
          if ((mname_len <= 3) || (method.getReturnType() == Void.TYPE) || ((!mname.startsWith("get")) && (!mname.startsWith("is"))))
          {
            Method m = ProxyModelObjectCreator.MapMethodCache.getMethod(mname, method.getParameterTypes());
            if (m != null) {
              return m.invoke(this.map, args);
            }
          }
        }
      }
      if ((method.getReturnType() != Void.TYPE) && ((args == null) || (args.length == 0)))
      {
        String property = ProxyModelObjectCreator.getPropertyName(method);
        if (property != null)
        {
          Object ret = this.introduceMap ? this.map.get(property) : proxy.invokeSuper(obj, args);
          if (ret != null) {
            return ret;
          }
          if (this.lazyresolve)
          {
            ModelPropertyDescriptor field = ModelPropertyDescriptor.get(method, true);
            if ((field.getField() != null) && (field.isAnnotationPresent(ModelRelation.class)))
            {
              Object rel = ModelFactoryUtil.resolveModelRelation(obj, this.proxyClass, field);
              field.setProperty(obj, rel);
              return rel;
            }
          }
          if ((ModelUtil.isMapBeanType(method.getReturnType())) || (method.getReturnType() == Options.class))
          {
            ret = ModelObjectCreator.get().create(method.getReturnType());
            setProperty(obj, property, ret);
          }
          return ProxyModelObjectCreator.convert(ret, method.getReturnType());
        }
      }
      try
      {
        return proxy.invokeSuper(obj, args);
      }
      catch (Exception e)
      {
        throw new RuntimeException("���������[" + this.proxyClass.getSimpleName() + "]���������[" + method.getName() + "]������!", e);
      }
    }
    
    private void setProperty(Object obj, String property, Object value)
    {
      if (this.introduceMap)
      {
        this.map.put(property, value);
      }
      else
      {
        ModelPropertyDescriptor pd = ModelPropertyDescriptor.get(this.proxyClass, property);
        pd.setProperty(obj, value);
      }
    }
  }
  
  private static Object convert(Object obj, Class<?> type)
  {
    return ConvertUtil.convert(obj, type);
  }
  
  private static Map<String, Object> NULL = new HashMap();
  private static Map<Class<?>, Map<String, Object>> prototypes = new ConcurrentHashMap();
  
  private static Map<String, Object> getPrototype(Class<?> type)
  {
    Map<String, Object> ret = (Map)prototypes.get(type);
    if (ret == NULL) {
      return null;
    }
    if (ret != null) {
      return ret;
    }
    ComplexType ctype = OdbFactory.getComplexType(type);
    if (ctype != null) {
      ret = getPrototype(ctype);
    }
    prototypes.put(type, ret == null ? NULL : ret);
    return ret;
  }
  
  private static Map<String, Object> getPrototype(ComplexType ctype)
  {
    Map<String, Object> ret = new LinkedHashMap();
    Object root = ModelUtil.getRoot(ctype);
    if (((root instanceof Schema)) && (((Schema)root).getClassgen() == Schema.Classgen.metadata)) {
      return null;
    }
    for (Element e : ctype.getAllElements())
    {
      Object initValue = null;
      if ((!ModelUtil.hasEnumeration(e.getTypeObj())) && ((e.getMulti() == null) || (!e.getMulti().booleanValue())))
      {
        SimpleType st = ModelUtil.getSimpleType(e.getTypeObj());
        if (st != null) {
          if (st.getJavaClass() == String.class) {
            initValue = "";
          } else if (st.getJavaClass() == Integer.class) {
            initValue = Integer.valueOf(0);
          } else if (st.getJavaClass() == Long.class) {
            initValue = Long.valueOf(0L);
          } else if (st.getJavaClass() == BigDecimal.class) {
            initValue = new BigDecimal("0.00");
          }
        }
      }
      ret.put(e.getId(), initValue);
      if (!(e instanceof DataInterfaceField))
      {
        Object value = getValue(e);
        if (value != null) {
          ret.put(e.getId(), value);
        }
      }
    }
    return ret;
  }
  
  private static final Map<String, Object> propCache = new ConcurrentHashMap();
  
  private static String getPropertyName(Method method)
  {
    String ret = null;
    if (!Modifier.isFinal(method.getModifiers()))
    {
      String property = method.getName();
      
      Object val = propCache.get(property);
      if (val == null)
      {
        if (property.startsWith("is")) {
          ret = uncapitalFirst(property.substring(2));
        } else if ((property.startsWith("get")) || (property.startsWith("set"))) {
          ret = uncapitalFirst(property.substring(3));
        } else {
          ret = property;
        }
        propCache.put(property, ret);
      }
      else
      {
        ret = (String)val;
      }
    }
    return ret;
  }
  
  private static String uncapitalFirst(String s)
  {
    return String.valueOf(s.charAt(0)).toLowerCase() + s.substring(1);
  }
  
  private static Object getValue(Element e)
  {
    String value = null;
    if ((e == null) || (e.getTypeObj() == null)) {
      return null;
    }
    if ((e.getFixedValue() != null) && (e.getFixedValue().trim().length() != 0)) {
      value = e.getFixedValue();
    } else if ((e.getDefaultValue() != null) && (e.getDefaultValue().trim().length() != 0)) {
      value = e.getDefaultValue();
    } else {
      return null;
    }
    return getValue(e, e.getTypeObj(), value);
  }
  
  private static Object getValue(Element e, ElementType typeObj, String value)
  {
    if (ModelUtil.hasEnumeration(typeObj))
    {
      RestrictionType rt = ModelUtil.getRestrictionType(typeObj);
      if ((rt.getBaseTypeObj() instanceof SimpleType))
      {
        Object ret = getValue(e, (SimpleType)rt.getBaseTypeObj(), value);
        Class retType = ModelUtil.getElementJavaClass(e);
        try
        {
          return EnumUtils.toEnum(retType, ret);
        }
        catch (Exception e1)
        {
          log.error("������������������������������������������" + getFullId(e) + "[" + retType.getName() + "][" + ret + "]", e1, new Object[0]);
          
          return null;
        }
      }
    }
    if (((typeObj instanceof RestrictionType)) && 
      ((((RestrictionType)typeObj).getBaseTypeObj() instanceof SimpleType))) {
      return getValue(e, (SimpleType)((RestrictionType)typeObj).getBaseTypeObj(), value);
    }
    if ((typeObj instanceof SimpleType))
    {
      SimpleType type = (SimpleType)typeObj;
      if (type.getJavaClass().isAssignableFrom(String.class)) {
        return value;
      }
      if (type.getJavaClass().isAssignableFrom(Long.class)) {
        return new Long(value);
      }
      if (type.getJavaClass().isAssignableFrom(BigDecimal.class)) {
        return new BigDecimal(value);
      }
    }
    return null;
  }
  
  private static String getFullId(ModelObject m)
  {
    String ret = m.getId();
    if ((m instanceof OwnerAware))
    {
      OwnerAware<ModelObject> o = (OwnerAware)m;
      if ((o.getOwner() instanceof ModelObject)) {
        ret = ret + "." + getFullId((ModelObject)o.getOwner());
      }
    }
    return ret;
  }
}
